﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Collections;
using System.DirectoryServices;
using System.DirectoryServices.Protocols;

namespace Rnwood.SmtpServer
{
    public class MessageProcessor
    {
        private static int MaxDeferLimit = 10;
        internal ConcurrentQueue<Message> deferQueue, bounceQueue, activeQueue;
        internal Hashtable userTable;
        internal Hashtable blockedDomains;
        public static int maxThreads = 4;
        public int currentThreads;
        private Server _server;


        /// <summary>
        /// Constructor for the Message Processor Module. Initializes all the queues and start the
        /// corresponding functions to empty those queues in a seperate thread. 
        /// </summary>
        internal MessageProcessor(Server s)
        {
            deferQueue = new ConcurrentQueue<Message>();
            bounceQueue = new ConcurrentQueue<Message>();
            activeQueue = new ConcurrentQueue<Message>();
            blockedDomains = new Hashtable();
            userTable = new Hashtable();
            currentThreads = 0;
            _server = s;

            new Thread(EmptyBounceQueue).Start();
            new Thread(EmptyDeferQueue).Start();

        }

        public void Start()
        {
            Message m;
            while (_server.MessageQueue.Count != 0)
            {
                while (_server.MessageQueue.TryDequeue(out m))
                {
                    Process(m);
                }
            }
        }

        /// <summary>
        /// Processes the message to be delivered. Checks for the validity of message 
        /// and runs an anti-virus check on it (simulation only)
        /// </summary>
        /// <param name="msg">Message to be processed</param>
        internal void Process(Message msg)
        {
            if (!ValidateMessage(ref msg) && !isSpam(msg))
                return;
            activeQueue.Enqueue(msg);
        }

        /// <summary>
        /// Checks whether a given message is spam or not
        /// </summary>
        /// <param name="msg">Message to be checked</param>
        /// <returns>True/False</returns>
        private bool isSpam(Message msg)
        {
            int sum = 0;
            foreach (byte b in msg.Data)
            {
                sum = (sum + (int)b) % 100;
            }
            if (sum % 10 == 0)
                return true;
            return false;
        }

        /// <summary>
        /// Method to empty the bounce queue. If the method finds a message to be bounced, 
        /// it processes it and checks again, otherwise sleeps for 5 seconds. This method 
        /// is run asynchronously in a new thread.
        /// </summary>
        internal void EmptyBounceQueue()
        {
            Message msg;
            while (true)
            {
                while (bounceQueue.TryDequeue(out msg))
                {
                }
                Thread.Sleep(5000);
            }
        }

        /// <summary>
        /// Method to empty the defer queue. While the method finds a message that has been defered,
        /// it increases its defer count and checks it against the max allowed limit. If the limit has
        /// not been crossed yet, it will put the message back in the active queue. Otherwise, it will
        /// create a bounce message and put that in the bounce queue. If the queue is empty, the thread
        /// sleeps for next half an hour.
        /// </summary>
        internal void EmptyDeferQueue()
        {
            Message msg;
            while (true)
            {
                while (deferQueue.TryDequeue(out msg))
                {
                    msg.DeferCount++;
                    if (msg.DeferCount > MaxDeferLimit)
                        bounceQueue.Enqueue(new Message(msg.From, "Unable to deliver message"));
                    else
                        activeQueue.Enqueue(msg);
                }
                Thread.Sleep(1800000);
            }
        }

        /// <summary>
        /// Validates the message which is being processed for following the 
        /// guidelines
        /// </summary>
        /// <param name="msg">Message to be processed</param>
        /// <returns>True / False based on whether the message is valid or not</returns>
        private bool ValidateMessage(ref Message msg)
        {
            int validMessages = 0;

            // Check whether the To Address mentioned in the message is a valid
            // address or not. If the address is not valid, create a bounce message.
            foreach (String toAddr in msg.To)
            {
                if (!isToAddressValid(toAddr))
                {
                    CreateBounceMessage(msg.From, "Unknown user: " + toAddr);
                    msg.ToList.Remove(toAddr);
                }
                else
                    validMessages++;
            }

            // Check whether the domain as mentioned in the From address is an allowed domain
            // or not. The server provides features to blacklist certain domains to cut down
            // on spam.
            if (!isDomainAllowed(msg.From))
            {
                CreateBounceMessage(msg.From, "Your domain is blocked");
                return false;
            }

            // If we have atleast one valid message (There may be multiple addresses in the 
            // To field), return true, otherwise false.
            if (validMessages > 0)
                return true;
            return false;
        }

        /// <summary>
        /// Enqueues the message in the bounce queue
        /// </summary>
        /// <param name="toAdd">Person whom the bounce message is being sent</param>
        /// <param name="reason">The reason for bouncing the message so that the user can take corrective actions</param>
        private void CreateBounceMessage(string toAdd, string reason)
        {
            Message msg = new Message(toAdd, reason);
            bounceQueue.Enqueue(msg);
        }

        // Check against the local user table to see whether its a valid address or not
        private bool isToAddressValid(String email)
        {
            try
            {
                DirectoryEntry entryRoot = new DirectoryEntry("LDAP://ldap.iiit.ac.in/ou=Users,dc=iiit,dc=ac,dc=in");
                entryRoot.AuthenticationType = AuthenticationTypes.Anonymous;
                DirectorySearcher searcher = new DirectorySearcher(entryRoot, "(mail=" + email + ")", new string[] { "dn", "mail" }, System.DirectoryServices.SearchScope.Subtree);

                SearchResultCollection result = searcher.FindAll();
                if (result.Count == 0)
                    return false;
                return true;
            }
            catch (Exception e)
            {
                return false;
            }
        }



        // Split the email address to obtain the domain and verify that it is not in the
        // blocked list.
        private bool isDomainAllowed(String email)
        {
            char[] delim = {'@'};
            email = email.Split(delim)[1];
            if (blockedDomains.ContainsKey(email))
                return false;
            return true;
        }

    }
}
